﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Administration.AccessControl;
using Microsoft.SharePoint.Utilities;

using SPaDevToolkit.Administration;
using SPaDevToolkit.Diagnostics;

using Northwind.Common.Diagnostics;

namespace Northwind.ServiceObjects
{
    [Guid("00D6487F-5247-4340-9E6B-E7740A6AD922")]
    [IisWebServiceApplicationBackupBehavior]
    public sealed class NorthwindApplication : SPIisWebServiceApplication, ISPaWebServiceApplication   // SPaCslaWebServiceApplication
    {
        #region Fields
        [Persisted]
        private NorthwindDatabase _NorthwindDatabase;

        #endregion

        #region Constants
        internal const string SupportedApplicationClassId = "6C12856E-3CA9-4D0D-85AD-FEA81DD830EE";
        public static string ServiceApplicationDefaultTypeName = "Northwind Service Application";
        public static string ServiceApplicationDefaultTypeDescription = "Northwind Service Application";
        public static string ServiceApplicationProxyDefaultTypeDescription = "Manages a Northwind";
        public static string ManageServiceApplicationLink = "/_layouts/Northwind/ManageNorthwindApplication.aspx";

        #endregion

        #region Constructors
        public NorthwindApplication()
        {
        }

		NorthwindApplication(string name, NorthwindService service, SPIisWebServiceApplicationPool applicationPool, NorthwindDatabase NorthwindDatabase)
			: base(name, service, applicationPool)
		{
            this._NorthwindDatabase = NorthwindDatabase;
		}
        #endregion

        #region Overrides
        public  List<ISPaWebServiceApplicationDatabase> Databases
        {
            get
            {
                List<ISPaWebServiceApplicationDatabase> databases = new List<ISPaWebServiceApplicationDatabase>();
                databases.Add(_NorthwindDatabase);
                return databases;
            }
        }

        public bool IsDatabaseNameInUse(string databaseName, string databaseServerName)
        {
            if (!string.IsNullOrEmpty(_NorthwindDatabase.Name) 
                && !string.IsNullOrEmpty(_NorthwindDatabase.ServiceInstance.NormalizedDataSource)
                && _NorthwindDatabase.Name.Equals(databaseName, StringComparison.OrdinalIgnoreCase) && _NorthwindDatabase.ServiceInstance.NormalizedDataSource.Equals(databaseServerName, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
            return false;
        }

        public override string DisplayName
        {
            get {
                if (!string.IsNullOrEmpty(base.Name))
                    return base.Name;
                else
                    return "Northwind Service Application";
            }
        }

        // NOTE: The type is displayed in the Service Applications UI in the central administration web site.
        public override string TypeName
        {
            get { return ServiceApplicationDefaultTypeName; }
        }

        public override Guid ApplicationClassId
        {
            get { return new Guid(SupportedApplicationClassId); }
        }

        public override SPAdministrationLink ManageLink
        {
            get { return new SPAdministrationLink(ManageServiceApplicationLink + "?ID=" + Id.ToString()); }
        }

        public override SPAdministrationLink PropertiesLink
        {
            //NOTE: Assuming the NorthwindService.CreateServiceApplicationLink page will support a create and edit mode in one page
            get { return new SPAdministrationLink(string.Format("{0}?ID={1}&Mode=Edit", NorthwindService.CreateServiceApplicationLink, Id.ToString())); }
        }

        protected override string DefaultEndpointName
        {
            get { return "http"; }
        }

        protected override string InstallPath
        {
            get { return SPUtility.GetGenericSetupPath(@"WebServices\Northwind"); }
        }

        protected override string VirtualPath
        {
            get { return "Northwind.svc"; }
        }
        
        protected override SPNamedCentralAdministrationRights[] AdministrationAccessRights
        {
            get
            {
                return new SPNamedCentralAdministrationRights[] { SPNamedCentralAdministrationRights.FullControl };
            }
        }

        protected override SPNamedIisWebServiceApplicationRights[] AccessRights
        {
            get
            {
                return new SPNamedIisWebServiceApplicationRights[] { SPNamedIisWebServiceApplicationRights.FullControl };
            }
        }



        public override void Provision()
        {
            Logger.WriteTrace(string.Format("Provisioning service application '{0}'", Name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
            Status = SPObjectStatus.Provisioning;
            Update();

            _NorthwindDatabase.Provision();

            ValidateDatabases();

            Install();

            base.Provision();

            Status = SPObjectStatus.Online;
            Update();
            Logger.WriteTrace(string.Format("Finished Provisioning service application '{0}'", Name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
        }

        private void Install()
        {
            //TODO: Install Timer jobs
        }

        private void ValidateDatabases()
        {
            //TODO: Perform any validation required on provisioning, for example ???
        }

        public override void Unprovision(bool deleteData)
        {
            Logger.WriteTrace(string.Format("Unprovisioning service application '{0}'", Name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
            try
            {
                if (SPObjectStatus.Unprovisioning != base.Status)
                {
                    Status = SPObjectStatus.Unprovisioning;
                    Update();
                }
                base.Unprovision(deleteData);

                Uninstall();

                if (deleteData && _NorthwindDatabase != null)
                    _NorthwindDatabase.Unprovision();

                Status = SPObjectStatus.Disabled;
                Update();
                Logger.WriteTrace(string.Format("Finished Unprovisioning service application '{0}'", Name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
            }
            catch (Exception ex)
            {
                Logger.WriteException(ex, "Error unprovisioning Northwind service application: " + Name, 0, EventSeverity.Error, AreaNames.Northwind, CategoryNames.Service);
                throw;
            }
        }

        private void Uninstall()
        {

        }

        //From UserProfileApplication
        public override void Update()
        {
            Logger.WriteTrace(string.Format("Updating service application '{0}'", Name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
            bool flag = false;
            try
            {
                if ((this._NorthwindDatabase != null) && !this._NorthwindDatabase.WasCreated)
                {
                    this._NorthwindDatabase.Update();
                    flag = true;
                }
                base.Update();
                Logger.WriteTrace(string.Format("Finished updating service application '{0}'", Name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
            }
            catch (Exception ex)
            {
                Logger.WriteException(ex, "Error updating Northwind service application: " + Name, 0, EventSeverity.Error, AreaNames.Northwind, CategoryNames.Service);
                throw;
            }
        }

        //From UserProfileApplication
        public override void Delete()
        {
            base.Delete();
            if (this._NorthwindDatabase != null)
            {
                this._NorthwindDatabase.Delete();
            }
        }

        protected override void OnProcessIdentityChanged(System.Security.Principal.SecurityIdentifier processSecurityIdentifier)
        {
            this.NorthwindDatabase.GrantDBOwnerAccess(processSecurityIdentifier);
            base.OnProcessIdentityChanged(processSecurityIdentifier);
        }
        #endregion

        #region Custom 
        public static NorthwindApplication Create(string name, NorthwindService service, SPIisWebServiceApplicationPool applicationPool, SPDatabaseParameters northwindDatabaseParameters)
        {
            NorthwindApplication serviceApplication = null;
            NorthwindDatabase northwindDatabase = null;

            Logger.WriteTrace(string.Format("Creating service application '{0}'", name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
            try
            {
                if (null == name)
                    throw new ArgumentNullException("name");
                if (null == service)
                    throw new ArgumentNullException("service");
                if (null == applicationPool)
                    throw new ArgumentNullException("applicationPool");

                string error;
                if (! IsServiceApplicationNameValid<NorthwindApplication, NorthwindService>(name, out error))
                {
                    throw new InvalidOperationException("Invalid service application name: " + error);
                }

                try
                {
                    NorthwindDatabaseManager dbManager = new NorthwindDatabaseManager();
                    northwindDatabase = dbManager.CreateDatabase<NorthwindDatabase>(northwindDatabaseParameters);
                }
                catch (SPDuplicateObjectException)
                {
                    throw new InvalidOperationException(string.Format("A database with the name {0} already exists", northwindDatabaseParameters.Database));
                }
                catch (Exception ex)
                {
                    throw;
                }

                serviceApplication = new NorthwindApplication(name, service, applicationPool, northwindDatabase);
                serviceApplication.Update();

                serviceApplication.AddServiceEndpoint("http", SPIisWebServiceBindingType.Http);
                serviceApplication.AddServiceEndpoint("https", SPIisWebServiceBindingType.Https, "secure");

                // NOTE: It seems redundant, but update needs to be called before AND after the endpoint gets provisioned.
                serviceApplication.Update();

                Logger.WriteTrace(string.Format("Finshed creating service application '{0}'", name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);

                return serviceApplication;
            }
            catch (Exception ex)
            {
                Logger.WriteException(ex, "Error creating Northwind Application: " + name, 0, EventSeverity.Error, AreaNames.Northwind, CategoryNames.Service);
                if (serviceApplication != null && serviceApplication.WasCreated)
                    serviceApplication.Delete();

                if(northwindDatabase != null && northwindDatabase.WasCreated)
                    northwindDatabase.Delete();
                
                throw;
            }
        }

        public void Update(string name, SPIisWebServiceApplicationPool appPool, SPDatabaseParameters northwindDatabaseParameters)
        {
            Logger.WriteTrace(string.Format("Updating service application '{0}' properties", name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
            try
            {
                base.Name = name;
                this.ApplicationPool = appPool;
                string databaseConnectionString = this._NorthwindDatabase.DatabaseConnectionString;

                if (northwindDatabaseParameters != null)
                {
                    Logger.WriteTrace("Updating service application database authorization", 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
                    this._NorthwindDatabase.UpdateDatabaseAuth(northwindDatabaseParameters);
                }

                this.Update();
                Logger.WriteTrace(string.Format("Finshed updating service application '{0}' properties", name), 0, TraceSeverity.High, AreaNames.Northwind, CategoryNames.Service);
            }
            catch (Exception ex)
            {
                Logger.WriteException(ex, "Error updating Northwind Application: " + name, 0, EventSeverity.Error, AreaNames.Northwind, CategoryNames.Service);

                throw;
            }
        }

        public new bool CheckAdministrationAccess(SPCentralAdministrationRights rights)
        {
            return base.CheckAdministrationAccess(rights);
        }

        public NorthwindDatabase NorthwindDatabase
        {
            get { return _NorthwindDatabase; }
        }

        public static bool IsServiceApplicationNameValid<SA, S>(string sharedAppName, out string errorMessage)
            where SA : SPIisWebServiceApplication
            where S : SPIisWebService
        {
            if (string.IsNullOrEmpty(sharedAppName))
            {
                throw new ArgumentNullException("sharedAppName");
            }

            bool flag = true;
            errorMessage = string.Empty;
            char[] anyOf = new char[] { ',', ':', '=' };

            if (sharedAppName.IndexOfAny(anyOf, 0) >= 0)
            {
                flag = false;
                errorMessage = "Invalid Characters found.";
            }

            if (flag)
            {
                foreach (SA application in GetApplications<SA, S>())
                {
                    if (application.Name.Equals(sharedAppName, StringComparison.OrdinalIgnoreCase))
                    {
                        flag = false;
                        errorMessage = "Duplicate Service Application Found";
                        return flag;
                    }
                }
            }

            return flag;
        }

        public static List<SA> GetApplications<SA, S>()
            where SA : SPIisWebServiceApplication
            where S : SPIisWebService
        {
            S service = SPFarm.Local.Services.GetValue<S>();
            List<SA> list = new List<SA>();

            if (service != null)
            {
                foreach (SPServiceApplication application in service.Applications)
                {
                    SA item = application as SA;
                    if (item != null)
                    {
                        list.Add(item);
                    }
                }
            }

            return list;
        }
        #endregion
    }
}
